import { Injectable } from '../decorators/inject';
import { TMergeCell } from '../types/common.interface';
import { Hook } from './hook';
import { clone } from '../utils/tools';
import { Config } from './config';
import { toJSON, Transform } from '../decorators/toJSON';
import { AddHook } from '../decorators/add-hook';

@Injectable('MergeCell')
@Transform()
export class MergeCell {
    @toJSON([])
    public mergeCells: TMergeCell[] = [
        // { row: -1, rowspan: -1, col: 2, colspan: 2}
        // {row: 1, rowspan: 2, col: -1, colspan: -1}
        // {row: -1, rowspan: -1, col: -1, colspan: -1}
    ];
    protected mergeCellReCordRender: Set<string> = new Set();
    constructor(
        protected hook: Hook,
        protected conf: Config,
    ) {
    }
    public init() {
        this.hook.beforeDataRender.add(() => {
            this.mergeCellReCordRender.clear();
        });
    }
    
    public addRenderedMergeCell(row: number, col: number) {
        this.mergeCellReCordRender.add(`${row}:${col}`);
    }
    public cellIsRender(row: number, col: number) {
        return this.mergeCellReCordRender.has(`${row}:${col}`);
    }
    public add(range: TMergeCell): boolean {
        let [newRange] = this.hook.beforeMergeCell.excute(range);
        if(newRange == null) {
            return false;
        }
        this._removeMergeCell(newRange);
        this.mergeCells.push(newRange);
        this.hook.afterMergeCell.excute(newRange);
        return true;
    }
    public remove(range: TMergeCell) {
        this._removeMergeCell(range);
    }
    private _removeMergeCell(range: TMergeCell) {
        let end = {
            row: range.row + range.rowspan,
            col: range.col + range.colspan
        }
        let removeMergeCell: TMergeCell[] = [];
        for(let mergeCell of this.mergeCells) {
            if(
                mergeCell.row >= range.row
                && mergeCell.col >= range.col
                && mergeCell.row + mergeCell.rowspan <= end.row
                && mergeCell.col + mergeCell.colspan <= end.col
            ) {
                removeMergeCell.push(mergeCell);
            }
        }
        for(let mergeCell of removeMergeCell) {
            for(let i = 0; i < this.mergeCells.length; i++) {
                if(this.mergeCells[i] == mergeCell) {
                    this.mergeCells.splice(i, 1);
                    break;
                }
            }
        }
    }
    /**
     * 
     * @param range1 TMergeCell
     * @param range2 TMergeCell
     * @description range1 在range2 中
     */
    public containMergeCell(range1:  TMergeCell, range2: TMergeCell) {
        if(range1.row === -1 || range1.col === -1) {
            if(range1.row === -1) {
                if(range2.col <= range1.col && range2.col + range2.colspan - 1 >= range1.col) {
                    return true;
                } else if(range2.col + range2.colspan >= range1.col + range1.colspan && range2.col <= range1.col + range1.colspan -1) {
                    return true;
                } else if(range2.col >= range1.col && range2.col + range2.colspan <= range1.col + range1.colspan) {
                    return true;
                }
            } else {
                if(range2.row <= range1.row && range2.row + range2.rowspan - 1 >= range1.row) {
                    return true;
                } else if(range2.row + range2.rowspan >= range1.row + range1.rowspan && range2.row <= range1.row + range1.rowspan - 1) {
                    return true;
                } else if(range2.row >= range1.row && range2.row + range2.rowspan <= range1.row + range1.rowspan) {
                    return true;
                }
            }
        }
        if(range1.row !== -1 && range1.col !== -1 && range2.row !== -1 && range2.col !== -1) {
            if(
                range1.row >= range2.row
                && range1.col >= range2.col
                && range1.row + range1.rowspan - 1 <= range2.row + range2.rowspan - 1
                && range1.col + range1.colspan - 1 <= range2.col + range2.colspan - 1
            ) {
                return true;
            }
        }
        return  false;
    }
    public hasUnion(range1:  TMergeCell, range2: TMergeCell) {
        let judge1 = range2.row >= range1.row  && range2.row <= range1.row + range1.rowspan - 1;
        let judge2 = range2.row + range2.rowspan - 1 >= range1.row && range2.row + range2.rowspan - 1 <= range1.row + range1.rowspan - 1;
        let judge3 = range2.col >= range1.col  && range2.col <= range1.col + range1.colspan - 1;
        let judge4 = range2.col + range2.colspan - 1 >= range1.col && range2.col + range2.colspan - 1 <= range1.col + range1.colspan - 1;
        let judge5 = range2.row <= range1.row + range1.rowspan - 1 && range2.row + range2.rowspan - 1 >= range1.row;
        let judge6 = range2.col <= range1.col + range1.colspan - 1 && range2.col + range2.colspan - 1 >= range1.col;
        if(
               (judge1 && judge6) 
            || (judge2 && judge6)
            || (judge3 && judge5)
            || (judge4 && judge5)
            ) {
            return true;
        }
        return false;
    }
    public unionRange(range: TMergeCell): TMergeCell {
        let start = {row: range.row, col: range.col};
        let end = {row: range.row + range.rowspan - 1, col: range.col + range.colspan - 1};
        for(let i = 0; i < this.mergeCells.length; i++) {
            let mergeCell = this.mergeCells[i];
            let changedRange = {row: start.row, col: start.col, rowspan: end.row - start.row + 1, colspan: end.col - start.col + 1};
            let hasUnion = this.hasUnion(changedRange, mergeCell);
            let isContent1 = this.containMergeCell(changedRange, mergeCell);
            let isContent2 = this.containMergeCell(mergeCell, changedRange);
            if(hasUnion || isContent1 || isContent2) {
                let mergeStart = {row: mergeCell.row,col: mergeCell.col};
                let mergerEnd = {row: mergeCell.row + mergeCell.rowspan - 1, col: mergeCell.col + mergeCell.colspan - 1};
                let needReset = false;
                if(start.row > mergeStart.row) {
                    start.row = mergeStart.row;
                    needReset = true;
                }
                if(start.col > mergeStart.col) {
                    start.col = mergeStart.col;
                    needReset = true;
                }
                if(end.row < mergerEnd.row) {
                    end.row = mergerEnd.row;
                    needReset = true;
                }
                if(end.col < mergerEnd.col) {
                    end.col = mergerEnd.col;
                    needReset = true;
                }
                if(needReset == true) {
                    i = -1;
                }
            }
        }
        return {
            row: start.row,
            col: start.col,
            rowspan: start.row === -1 ? -1 : end.row - start.row + 1,
            colspan: start.col === -1 ? -1 : end.col - start.col + 1
        }
    }
    public getMergedCells(): TMergeCell[] {
        return clone(this.mergeCells);
    }
    public setMergedCells(ranges: TMergeCell[]): void {
        this.mergeCells = ranges;
    }
    public visibleCell(row: number, col: number): TMergeCell {
        let range1 = {row: row, col: col, rowspan: 1, colspan: 1};
        for(let mergeCell of this.mergeCells) {
            if(this.containMergeCell(range1, mergeCell) == true) {
                return mergeCell;
            }
        }
        return {row: row, col: col, rowspan: 1, colspan: 1};
    }
    public getMerge(row: number, col: number): TMergeCell {
        let range1 = {row: row, col: col, rowspan: 1, colspan: 1};
        for(let mergeCell of this.mergeCells) {
            if(this.containMergeCell(range1, mergeCell) == true) {
                return mergeCell;
            }
        }
        return null;
    }
    public spliteMerge(range: TMergeCell) {
        let {row: freezeRow, col: freezeCol} = this.conf.freeze;
        let results: TMergeCell[] = [{...range}];
        let newRes: TMergeCell[] = [];
        for(let i = 0; i < results.length; i++) {
            let _range = results[i];
            if(_range.row <= freezeRow && _range.row + _range.rowspan - 1 > freezeRow) {
                newRes.push({
                    row: _range.row, 
                    col: _range.col, 
                    rowspan: freezeRow - _range.row + 1, 
                    colspan: _range.colspan
                });
                newRes.push({
                    row: freezeRow + 1, 
                    col: _range.col, 
                    rowspan: _range.row + _range.rowspan - 1 - freezeRow,
                    colspan: _range.colspan
                })
            } else {
                newRes.push(_range);
            }
        }
        let newRes1: TMergeCell[] = [];
        for(let i = 0; i < newRes.length; i++) {
            let _range = newRes[i];
            if(_range.col <= freezeCol && _range.col + _range.colspan - 1 > freezeCol) {
                newRes1.push({
                    row: _range.row,
                    col: _range.col,
                    rowspan: _range.rowspan,
                    colspan: freezeCol - _range.col + 1
                });
                newRes1.push({
                    row: _range.row,
                    col: freezeCol + 1,
                    rowspan: _range.rowspan,
                    colspan: _range.col + _range.colspan - 1 - freezeCol
                });
            } else {
                newRes1.push(_range);
            }
        }
        return newRes1;
    }
    /**
     * 
     * @param mergeCell 
     * @returns 
     * |1|2
     * |3|4
     */
    public getAfterSplitMergeQuadrant(mergeCell: TMergeCell): 1|2|3|4 {
        let {row: freezeRow, col: freezeCol} = this.conf.freeze;
        if(mergeCell.row + mergeCell.rowspan - 1 <= freezeRow) {
            if(mergeCell.col + mergeCell.colspan - 1 <= freezeCol) {
                return 1;
            } else {
                return 2;
            }
        } else {
            if(mergeCell.col + mergeCell.colspan - 1 <= freezeCol) {
                return 3;
            } else {
                return 4;
            }
        }
    }
    public getAfterSplitMergeRectMerge<T extends TMergeCell>(mergeCell: T): T {
        const {row: freezeRow, col: freezeCol} = this.conf.freeze;
        let quadrant = this.getAfterSplitMergeQuadrant(mergeCell);
        if(quadrant === 1) {
            if(mergeCell.row + mergeCell.rowspan - 1 > freezeRow) {
                mergeCell.rowspan = mergeCell.rowspan - (mergeCell.row + mergeCell.rowspan - 1 - freezeRow);
            }
            if(mergeCell.col + mergeCell.colspan - 1 > freezeCol) {
                mergeCell.colspan = mergeCell.colspan - (mergeCell.col + mergeCell.colspan - 1 - freezeCol);
            }
        }
        if(quadrant === 2 || quadrant === 4) {
            if(mergeCell.col > this.conf.visibleBoundary.endCol || mergeCell.col + mergeCell.colspan - 1 < this.conf.visibleBoundary.col) {
                return null;
            }
            if(mergeCell.col < this.conf.visibleBoundary.col) {
                let oldCol = mergeCell.col;
                mergeCell.col = this.conf.visibleBoundary.col;
                mergeCell.colspan = mergeCell.colspan - (this.conf.visibleBoundary.col - oldCol)
            }
            if(mergeCell.col + mergeCell.colspan - 1 > this.conf.visibleBoundary.endCol) {
                mergeCell.colspan = mergeCell.colspan - (mergeCell.col + mergeCell.colspan - 1 - this.conf.visibleBoundary.endCol);
            }
        }
        if(quadrant === 4 || quadrant === 3) {
            if(mergeCell.row > this.conf.visibleBoundary.endRow || mergeCell.row + mergeCell.rowspan - 1 < this.conf.visibleBoundary.row) {
                return null;
            }
            if(mergeCell.row < this.conf.visibleBoundary.row) {
                let oldRow = mergeCell.row;
                mergeCell.row = this.conf.visibleBoundary.row;
                mergeCell.rowspan = mergeCell.rowspan - (this.conf.visibleBoundary.row - oldRow)
            }
            if(mergeCell.row + mergeCell.rowspan - 1 > this.conf.visibleBoundary.endRow) {
                mergeCell.rowspan = mergeCell.rowspan - (mergeCell.row + mergeCell.rowspan - 1 - this.conf.visibleBoundary.endRow);
            }
        }
        return mergeCell;
    }
    @AddHook('afterDeleteRow')
    protected mergeCellsChangeByDeleteRow(row: number) {
        this.mergeCells = this.mergeCells.map(merge => {
            if(row >= merge.row && row < merge.row + merge.rowspan) {
                if(merge.rowspan === 2 && merge.colspan === 1) {
                    return null;
                }
                merge.rowspan = merge.rowspan - 1;
            }
            if(row < merge.row) {
                merge.row = merge.row - 1;
            }
            return merge;
        }).filter(item => item !== null)
    }
    @AddHook('afterDeleteCol')
    protected mergeCellsChangeByDeleteCol(col: number) {
        this.mergeCells = this.mergeCells.map(merge => {
            if(col >= merge.col && col < merge.col + merge.colspan) {
                if(merge.colspan === 2 && merge.rowspan === 1) {
                    return null;
                }
                merge.colspan = merge.colspan - 1;
            }
            if(merge.col > col) {
                merge.col = merge.col - 1;
            }
            return merge;
        }).filter(item => item !== null);
    }
    @AddHook('afterInsertRow')
    protected mergeCellsChangeByInsertRow(row: number) {
        this.mergeCells = this.mergeCells.map(merge => {
            if(row >= merge.row && row < merge.row + merge.rowspan) {
                merge.rowspan = merge.rowspan + 1;
            }
            if(row < merge.row) {
                merge.row = merge.row + 1;
            }
            return merge;
        });
    }
    @AddHook('afterInsertCol')
    protected mergeCellsChangeByInsertCol(col: number) {
        this.mergeCells = this.mergeCells.map(merge => {
            if(col >= merge.col && col < merge.col + merge.colspan) {
                merge.colspan = merge.colspan + 1;
            }
            if(col < merge.col) {
                merge.col = merge.col + 1;
            }
            return merge;
        });
    }
}